//////////////////////////////////////////////
// PbsProgramTracker.h
//
//////////////////////////////////////////////

/// Defines / Macros -------------------------

#pragma once

/// Forward decl -----------------------------

namespace nkAstraeus
{
	struct PbsMaterialInfo ;
}

namespace nkGraphics
{
	class Program ;
	class System ;
}

/// Includes ---------------------------------

// nkCommon
#include <nilkinsCommon/Patterns/SingletonClass.h>

// nkGraphics
#include <NilkinsGraphics/Programs/Program.h>
#include <NilkinsGraphics/Programs/ProgramManager.h>

// nkMemory
#include <NilkinsMemory/Pointers/UniquePtr.h>

// Standards
#include <unordered_map>

/// Internals --------------------------------

namespace nkAstraeus
{
	struct ProgramEntry
	{
		nkMemory::UniquePtr<nkGraphics::Program> _program ;
		unsigned int _count = 0 ;
	} ;
}

/// Class ------------------------------------

namespace nkAstraeus
{
	template <typename T_info, typename T_idGenerator, typename T_nameBase>
	class ProgramTracker
	{
		public :

			// Management
			nkGraphics::Program* getProgram (const T_info& info)
			{
				// Find back the index first
				unsigned long long index = T_idGenerator::generate(info) ;

				std::unordered_map<unsigned long long, ProgramEntry>::iterator searchResult = _programs.find(index) ;

				if (searchResult != _programs.end())
				{
					// Increment count and return
					searchResult->second._count++ ;
					return searchResult->second._program.get() ;
				}

				// Need to create it
				std::string programName (T_nameBase::name) ;
				programName += std::to_string(index) ;

				nkMemory::UniquePtr<nkGraphics::Program> resource = nkGraphics::Program::create(_graphicsSystem) ;
				resource->setName(programName) ;
				nkGraphics::Program* result = resource.get() ;

				// Register
				_programs.emplace(index, ProgramEntry{std::move(resource), 1}) ;

				// And return this new entry
				return result ;
			}

			void releaseProgram (const T_info& info)
			{
				unsigned long long index = T_idGenerator::generate(info) ;

				std::unordered_map<unsigned long long, ProgramEntry>::iterator searchResult = _programs.find(index) ;

				if (searchResult != _programs.end())
				{
					// Decrement and check value, erase if needed
					--searchResult->second._count ;

					if (!searchResult->second._count)
						_programs.erase(searchResult) ;
				}
			}

		protected :

			// Functions
			// Constructor, destructor
			ProgramTracker (nkGraphics::System* graphicsSystem) noexcept
			:	_graphicsSystem (graphicsSystem),
				_programs ()
			{
				// Nothing to do
			}

			ProgramTracker (const ProgramTracker&) = delete ;

			ProgramTracker (ProgramTracker&&) = delete ;

			virtual ~ProgramTracker () = default ;

			// Operators
			ProgramTracker& operator= (const ProgramTracker&) = delete ;

			ProgramTracker& operator= (ProgramTracker&&) = delete ;

		protected :

			// Attributes
			nkGraphics::System* _graphicsSystem ;

			// Memory of programs
			std::unordered_map<unsigned long long, ProgramEntry> _programs ;
	} ;
}